/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"

#ifdef WIN32

/****************** Capturing video from camera via CMU lib *******************/

#if HAVE_CMU1394

// This firewire capability added by Philip Gruebele (pgruebele@cox.net).
// For this to work you need to install the CMU firewire DCAM drivers,
// located at http://www-2.cs.cmu.edu/~iwan/1394/.
#include "1394camera.h"

class CvCaptureCAM_CMU : public CvCapture {
public:
	CvCaptureCAM_CMU() {
		index = -1;
		image = 0;
	}

	virtual ~CvCaptureCAM_CMU() {
		close();
	}

	virtual bool open(int cameraId);
	virtual void close();
	virtual double getProperty(int);
	virtual bool setProperty(int, double);
	virtual bool grabFrame();
	virtual IplImage* retrieveFrame(int);

protected:
	C1394Camera* camera();
	CvSize getSize();
	int getDepth();
	int getNChannels();

	bool setVideoSize(int, int);
	bool setMode(int mode);
	bool setFrameRate(int rate);
	bool setFormat(int format);

	int  fps;    // 0-5
	int  mode;   // 0-7
	int  format; // 0-2, 7 ?
	int  index;
	IplImage* image;
};

// CMU 1394 camera stuff.
// This firewire capability added by Philip Gruebele (pgruebele@cox.net)
// and modified by Roman Stanchak (rstanchak@yahoo.com).
// For this to work you need to install the CMU firewire DCAM drivers,
// located at http://www-2.cs.cmu.edu/~iwan/1394/.
#define CMU_MAX_CAMERAS     20
int             CMU_numCameras = 0;
int             CMU_numActiveCameras = 0;
bool            CMU_useCameraFlags[CMU_MAX_CAMERAS];
C1394Camera*     CMU_theCamera = 0;

// stupid defines for mode, format, FPS
#define CV_CAP_IEEE1394_FPS_1_875 0
#define CV_CAP_IEEE1394_FPS_3_75 1
#define CV_CAP_IEEE1394_FPS_7_5 2
#define CV_CAP_IEEE1394_FPS_15 3
#define CV_CAP_IEEE1394_FPS_30 4
#define CV_CAP_IEEE1394_FPS_60 5

// index by size, color
#define CV_CAP_IEEE1394_COLOR_MONO 0
#define CV_CAP_IEEE1394_COLOR_MONO16 1
#define CV_CAP_IEEE1394_COLOR_YUV444 2
#define CV_CAP_IEEE1394_COLOR_YUV422 3
#define CV_CAP_IEEE1394_COLOR_YUV411 4
#define CV_CAP_IEEE1394_COLOR_RGB 5

#define CV_CAP_IEEE1394_SIZE_160X120 0
#define CV_CAP_IEEE1394_SIZE_320X240 1
#define CV_CAP_IEEE1394_SIZE_640X480 2
#define CV_CAP_IEEE1394_SIZE_800X600 3
#define CV_CAP_IEEE1394_SIZE_1024X768 4
#define CV_CAP_IEEE1394_SIZE_1280X960 5
#define CV_CAP_IEEE1394_SIZE_1600X1200 6

// given color, size, output format
// 1 16  444 422 411 RGB
static char CV_CAP_IEEE1394_FORMAT[7][6] = {
	{ -1, -1,  0, -1, -1, -1}, // 160x120
	{ -1, -1, -1,  0, -1, -1}, // 320x240
	{ 0,  0, -1,  0,  0,  0}, // 640x480
	{ 1,  1, -1,  1, -1,  1}, // 800x600
	{ 1,  1, -1,  1, -1,  1}, // 1024x768
	{ 2,  2, -1,  2, -1,  2}, // 1280x960
	{ 2,  2, -1,  2, -1,  2}  // 1600x1200
};

// given color, size, output corresponding mode
static char CV_CAP_IEEE1394_MODE[7][6] = {
	{ -1, -1,  0, -1, -1, -1}, // 160x120
	{ -1, -1, -1,  1, -1, -1}, // 320x240
	{ 5,  6, -1,  3,  2,  4}, // 640x480
	{ 2,  6, -1,  0, -1,  1}, // 800x600
	{ 5,  7, -1,  3, -1,  4}, // 1024x768
	{ 2,  6, -1,  0, -1,  1}, // 1280x960
	{ 5,  7, -1,  3, -1,  4}  // 1600x1200
};

// given format, mode, return COLOR
static char CV_CAP_IEEE1394_COLOR[2][8] = {
	{
		CV_CAP_IEEE1394_COLOR_YUV444,
		CV_CAP_IEEE1394_COLOR_YUV422,
		CV_CAP_IEEE1394_COLOR_YUV411,
		CV_CAP_IEEE1394_COLOR_YUV422,
		CV_CAP_IEEE1394_COLOR_RGB,
		CV_CAP_IEEE1394_COLOR_MONO,
		CV_CAP_IEEE1394_COLOR_MONO16
	},
	{
		CV_CAP_IEEE1394_COLOR_YUV422,
		CV_CAP_IEEE1394_COLOR_RGB,
		CV_CAP_IEEE1394_COLOR_MONO,
		CV_CAP_IEEE1394_COLOR_YUV422,
		CV_CAP_IEEE1394_COLOR_RGB,
		CV_CAP_IEEE1394_COLOR_MONO,
		CV_CAP_IEEE1394_COLOR_MONO16,
		CV_CAP_IEEE1394_COLOR_MONO16
	}
};

// convert frame rate to suitable enum
/*static int icvFrameRateToIndex_CMU(double framerate){
    if(framerate > 30)       return CV_CAP_IEEE1394_FPS_60;
    else if(framerate > 15)  return CV_CAP_IEEE1394_FPS_30;
    else if(framerate > 7.5) return CV_CAP_IEEE1394_FPS_15;
    else if(framerate > 3.75) return CV_CAP_IEEE1394_FPS_7_5;
    else if(framerate > 1.875) return CV_CAP_IEEE1394_FPS_3_75;
    return CV_CAP_IEEE1394_FPS_1_875;
}*/

#if _MSC_VER >= 1200
#pragma comment(lib,"1394camera.lib")
#endif

C1394Camera* CvCaptureCAM_CMU::camera() {
	return CMU_theCamera && index >= 0 ? &CMU_theCamera[index] : 0;
}

// return the size of the image
CvSize CvCaptureCAM_CMU::getSize() {
	C1394Camera* cmucam = camera();
	unsigned long width = 0, height = 0;
	cmucam->GetVideoFrameDimensions( &width, &height );
	return cvSize((int)width, (int)height);
}

// return the opencv depth flag corresponding to the camera format
int CvCaptureCAM_CMU::getDepth() {
	C1394Camera* cmucam = camera();
	int format = cmucam->GetVideoFormat();
	int mode = cmucam->GetVideoMode();

	// TODO
	if ( format == 7 ) {
		assert(0);
		return 1;
	}
	// irrelvant to depth
	if ( format > 1 ) {
		format = 1;
	}

	if ( CV_CAP_IEEE1394_COLOR[format][mode] == CV_CAP_IEEE1394_COLOR_MONO16 ) {
		return IPL_DEPTH_16S;
	}

	return IPL_DEPTH_8U;
}

// return the number of channels for camera
int CvCaptureCAM_CMU::getNChannels() {
	C1394Camera* cmucam = camera();
	int format = cmucam->GetVideoFormat();
	int mode = cmucam->GetVideoMode();

	if ( format == 7 ) {
		assert(0);
		return 1;
	}

	// irrelvant to nchannels
	if ( format > 1 ) {
		format = 1;
	}

	switch (CV_CAP_IEEE1394_COLOR[format][mode]) {
	case CV_CAP_IEEE1394_COLOR_RGB:
		return 3;
	case CV_CAP_IEEE1394_COLOR_MONO:
	case CV_CAP_IEEE1394_COLOR_MONO16:
		return 1;
	case CV_CAP_IEEE1394_COLOR_YUV422:
	case CV_CAP_IEEE1394_COLOR_YUV444:
	case CV_CAP_IEEE1394_COLOR_YUV411:
		return 3;
	default:
		;
	}
	return -1;
}

bool CvCaptureCAM_CMU::open( int _index ) {
	close();

	// if first time, then allocate all available cameras
	if ( CMU_numCameras == 0 ) {
		CMU_numActiveCameras = 0;
		CMU_theCamera = new C1394Camera[CMU_MAX_CAMERAS];

		////////////////////////////////////////////////////////////////////////////////////////////////////////
		// create all cameras
		try {
			// create camera0
			if ( CMU_theCamera[0].CheckLink() != CAM_SUCCESS ) {
				throw 1;
			}

			// we have one pin per camera
			CMU_numCameras = CMU_theCamera[0].GetNumberCameras();

			// allocate remaining cameras
			for (int i = 1; i < CMU_numCameras && i < CMU_MAX_CAMERAS; i++ ) {
				CMU_useCameraFlags[i] = false;
				if (CMU_theCamera[i].CheckLink() != CAM_SUCCESS) {
					throw 1;
				}
			}
		} catch (...) {
			// free any allocated cameras
			// ...
			CMU_numCameras = 0;
			return false;
		}
	}

	try {
		CvSize size;

		// pick first unused camera
		if (_index == -1) {
			for (int i = 0; i < CMU_numCameras; i++ ) {
				if ( !CMU_useCameraFlags[i] ) {
					_index = i;
					break;
				}
			}
		}

		// no empty camera found
		if (_index == -1) {
			throw 1;
		}

		if (CMU_theCamera[_index].SelectCamera(_index) != CAM_SUCCESS) {
			throw 2;
		}

		if (CMU_theCamera[_index].InitCamera() != CAM_SUCCESS) {
			throw 3;
		}

		// set initial format -- try to pick best frame rate first, then color, then size
		bool found_format = false;
		for (int rate = 5; rate >= 0 && !found_format; rate--) {
			for (int color = CV_CAP_IEEE1394_COLOR_RGB; color >= 0 && !found_format; color--) {
				for (int size = CV_CAP_IEEE1394_SIZE_1600X1200; size >= 0 && !found_format; size--) {
					int format = CV_CAP_IEEE1394_FORMAT[size][color];
					int mode = CV_CAP_IEEE1394_MODE[size][color];
					if (format != -1 && mode != -1 &&
							CMU_theCamera[_index].HasVideoFrameRate(format, mode, rate)) {
						CMU_theCamera[_index].SetVideoFormat(format);
						CMU_theCamera[_index].SetVideoMode(mode);
						CMU_theCamera[_index].SetVideoFrameRate(rate);
						found_format = (CMU_theCamera[_index].StartImageAcquisition() == CAM_SUCCESS);
					}
				}
			}
		}

		// try format 7
		if (!found_format) {
			CMU_theCamera[_index].SetVideoFormat(7);
			CMU_theCamera[_index].SetVideoMode(0);
			if (CMU_theCamera[_index].StartImageAcquisition() != CAM_SUCCESS) {
				// no format found
				throw 9;
			}
		}

		index = _index;
		size = getSize();
		// allocate image frame
		image = cvCreateImage( size, 8, 3 );
		cvZero(image);

		// successfully activated camera
		CMU_numActiveCameras++;
		CMU_useCameraFlags[_index] = true;
	} catch ( int ) {
		return false;
	}

	return true;
}

void CvCaptureCAM_CMU::close() {
	C1394Camera* cmucam = camera();
	if ( cmucam ) {
		cvReleaseImage( &image );
		cmucam->StopImageAcquisition();
		CMU_useCameraFlags[index] = false;
		index = -1;

		if ( --CMU_numActiveCameras == 0 ) {
			delete[] CMU_theCamera;
			CMU_theCamera = 0;
			CMU_numCameras = 0;
		}
	}
}


bool CvCaptureCAM_CMU::grabFrame() {
	C1394Camera* cmucam = camera();
	return cmucam ? cmucam->AcquireImage() == CAM_SUCCESS : false;
}

/*static void swapRedBlue(IplImage * im)
{
	uchar * ptr = (uchar *) im->imageData;
	uchar t;
	for(int i=0; i<im->height; i++){
		ptr = (uchar *) im->imageData+im->widthStep*i;
		for(int j=0; j<im->width; j++){
			t = ptr[0];
			ptr[0] = ptr[2];
			ptr[2] = t;
			ptr+=3;
		}
	}
}*/

IplImage* CvCaptureCAM_CMU::retrieveFrame(int) {
	C1394Camera* cmucam = camera();
	if ( !cmucam ) {
		return 0;
	}
	cmucam->getRGB((uchar*)image->imageData, image->imageSize);
	cvConvertImage( image, image, CV_CVTIMG_SWAP_RB );
	return image;
}


double CvCaptureCAM_CMU::getProperty( int property_id ) {
	C1394Camera* cmucam = camera();
	if ( !cmucam ) {
		return 0;
	}
	switch ( property_id ) {
	case CV_CAP_PROP_FRAME_WIDTH:
		return image->width;
	case CV_CAP_PROP_FRAME_HEIGHT:
		return image->height;
	case CV_CAP_PROP_FPS:
		return cmucam->GetVideoFrameRate();
	case CV_CAP_PROP_MODE:
		return cmucam->GetVideoMode();
	case CV_CAP_PROP_FORMAT:
		return cmucam->GetVideoFormat();
	}
	return 0;
}

bool CvCaptureCAM_CMU::setVideoSize(int, int) {
	return false;
}

bool CvCaptureCAM_CMU::setMode(int mode) {
	int format;
	C1394Camera* cmucam = camera();
	if ( !cmucam ) {
		return false;
	}
	format = cmucam->GetVideoFormat();
	if ( mode < 0 || mode > 7 || !cmucam->HasVideoMode(format, mode)) {
		return false;
	}
	cmucam->StopImageAcquisition();
	cmucam->SetVideoMode(mode);
	cmucam->StartImageAcquisition();
	return true;
}

bool CvCaptureCAM_CMU::setFrameRate(int rate) {
	int format, mode;
	C1394Camera* cmucam = camera();
	if ( !cmucam ) {
		return false;
	}
	mode = cmucam->GetVideoMode();
	format = cmucam->GetVideoFormat();
	if ( rate < 0 || rate > 5 || !cmucam->HasVideoFrameRate(format, mode, rate) ) {
		return false;
	}
	cmucam->StopImageAcquisition();
	cmucam->SetVideoFrameRate(rate);
	cmucam->StartImageAcquisition();
	return true;
}

bool CvCaptureCAM_CMU::setFormat(int format) {
	C1394Camera* cmucam = camera();
	if ( !cmucam ) {
		return false;
	}
	if ( format < 0 || format > 2 || !cmucam->HasVideoFormat(format) ) {
		return false;
	}
	cmucam->StopImageAcquisition();
	cmucam->SetVideoFormat(format);
	cmucam->StartImageAcquisition();
	return true;
}

bool CvCaptureCAM_CMU::setProperty( int property_id, double value ) {
	bool retval = false;
	int ival = cvRound(value);
	C1394Camera* cmucam = camera();
	if ( !cmucam ) {
		return false;
	}

	switch (property_id) {
	case CV_CAP_PROP_FRAME_WIDTH:
	case CV_CAP_PROP_FRAME_HEIGHT: {
		int width, height;
		if (property_id == CV_CAP_PROP_FRAME_WIDTH) {
			width = ival;
			height = width * 3 / 4;
		} else {
			height = ival;
			width = height * 4 / 3;
		}
		retval = setVideoSize(width, height);
	}
	break;
	case CV_CAP_PROP_FPS:
		retval = setFrameRate(ival);
		break;
	case CV_CAP_PROP_MODE:
		retval = setMode(ival);
		break;
	case CV_CAP_PROP_FORMAT:
		retval = setFormat(ival);
		break;
	}

	// resize image if its not the right size anymore
	CvSize size = getSize();
	if ( !image || image->width != size.width || image->height != size.height ) {
		cvReleaseImage( &image );
		image = cvCreateImage( size, 8, 3 );
	}
	return retval;
}

CvCapture* cvCreateCameraCapture_CMU (int index) {
	CvCaptureCAM_CMU* capture = new CvCaptureCAM_CMU;
	if ( capture->open(index) ) {
		return capture;
	}
	delete capture;
	return 0;
}

#endif // CMU
#endif // WIN32
